package com.jeasonzhao.commons.parser.expression;

import com.jeasonzhao.commons.parser.lex.LexException;
import com.jeasonzhao.commons.utils.Algorithms;
import com.jeasonzhao.commons.utils.DataTypes;
import com.jeasonzhao.commons.utils.RegexHelper;

public abstract class AbstractEvalProvider implements IEvalProvider
{
    private boolean m_isIgnoreCase = true;
    //private static final Log log = LogFactory.getLog(BasicEvalProvider.class);
    private java.util.Hashtable<String,ValuePair> m_variables
        = new java.util.Hashtable<String,ValuePair>();
    private java.util.Hashtable<String,IMethodDefination> m_hashFunctionNames2Sign
        = new java.util.Hashtable<String,IMethodDefination>();
    private java.util.ArrayList<IEvalProvider> m_providersChain = new java.util.ArrayList<IEvalProvider>();
    public AbstractEvalProvider()
    {
        this(true);
    }

    public AbstractEvalProvider(boolean ignoreCase)
    {
        m_isIgnoreCase = ignoreCase;
    }

    public boolean isIgnoreCase()
    {
        return m_isIgnoreCase;
    }

    public void clearVariables()
    {
        if(null != m_variables)
        {
            m_variables.clear();
        }
    }

    private String wrapName(String s)
    {
        if(this.isIgnoreCase())
        {
            return null == s ? null : s.toLowerCase();
        }
        else
        {
            return s;
        }
    }

    public void removeVariablesByRegex(String strRegex)
    {
        if(null == strRegex || strRegex.length() < 1)
        {
            return;
        }
        for(java.util.Enumeration<String> er = m_variables.keys();er != null && er.hasMoreElements();)
        {
            String strVariableName = er.nextElement();
            if(this.isIgnoreCase())
            {
                if(RegexHelper.matchesIgnoreCase(strRegex,strVariableName))
                {
                    m_variables.remove(strVariableName);
                }
            }
            else
            {
                if(RegexHelper.matches(strRegex,strVariableName))
                {
                    m_variables.remove(strVariableName);
                }
            }
        }
    }

    public void removeVariable(String strVariableName)
    {
        strVariableName = wrapName(strVariableName);
        if(strVariableName == null)
        {
            return;
        }
        this.m_variables.remove(strVariableName);
    }

    public void addVariable(String strName,ValuePair p)
    {
        if(null == p || Algorithms.isEmpty(strName))
        {
            return;
        }
        this.m_variables.put(this.wrapName(strName),p);
    }

    public void addVariable(String strName,Object value)
    {
        this.addVariable(strName,new ValuePair(value));
    }

    public void addVariable(String strName,Object value,DataTypes dataType)
    {
        this.addVariable(strName,new ValuePair(value,dataType));
    }

    public ValuePair evalFunction(String strFunctionName,ValuePair[] parameters)
        throws EvalException
    {
        IMethodDefination fun = this.getMethodDefination(strFunctionName,parameters);
        if(null == fun)
        {
            throw new FunctionNotFoundException("No such method: " + strFunctionName);
        }
        return fun.invoke(parameters);
    }

    public ValuePair evalVariable(String strExpression)
        throws EvalException
    {
        strExpression = wrapName(strExpression);
        if(null == strExpression)
        {
            throw new EvalException("The expression cannot be empty.");
        }
        if(null != m_variables && m_variables.containsKey(strExpression))
        {
            return(ValuePair) m_variables.get(strExpression);
        }
        if(null != m_providersChain)
        {
            for(int n = m_providersChain.size() - 1;n >= 0;n--)
            {
                IEvalProvider p = (IEvalProvider) m_providersChain.get(n);
                if(p != null)
                {
                    try
                    {
                        return p.evalVariable(strExpression);
                    }
                    catch(EvalException ex)
                    {
                    	ex.printStackTrace();
                    }
                }
            }
        }
        throw new VariableNotFoundException("No such variable: " + strExpression);
    }

    public IMethodDefination getMethodDefination(String strFunctionName,ValuePair[] parameters)
    {
        String[] keys = EvalProviderHelper.getFunctionSignKeys(strFunctionName,parameters);
        if(null == keys)
        {
            return null;
        }
        for(int n = 0;n < keys.length;n++)
        {
            String s = wrapName(keys[n]);
            if(m_hashFunctionNames2Sign.containsKey(s))
            {
                return(IMethodDefination) m_hashFunctionNames2Sign.get(s);
            }
        }
        if(null != m_providersChain)
        {
            for(int n = m_providersChain.size() - 1;n >= 0;n--)
            {
                IEvalProvider p = (IEvalProvider) m_providersChain.get(n);
                if(p != null)
                {
                    IMethodDefination is = p.getMethodDefination(strFunctionName,parameters);
                    if(null != is)
                    {
                        return is;
                    }
                }
            }
        }
        return null;
    }

    public SyntaxNode parseExpression(String strExpression)
        throws SyntaxException,LexException
    {
        ExpressionParser p = new ExpressionParser(strExpression);
        return p.parseNode();
    }

    public void addFunction(String key,IMethodDefination sign)
    {
        if(null != sign && null != key)
        {
            this.m_hashFunctionNames2Sign.put(wrapName(key),sign);
        }
    }

    public void addProvider(IEvalProvider p)
    {
        if(null != p)
        {
            m_providersChain.remove(p);
            m_providersChain.add(p);
        }
    }

    public void removeProvider(IEvalProvider p)
    {
        m_providersChain.remove(p);
    }

    public ValuePair eval(String strExpression)
        throws EvalException
    {
        try
        {
            SyntaxNode node = parseExpression(strExpression);
            return null == node ? null : node.eval(this);
        }
        catch(LexException ex)
        {
            throw new EvalException(strExpression + ":" + ex.getMessage(),ex);
        }
        catch(SyntaxException ex)
        {
            throw new EvalException(strExpression + ":" + ex.getMessage(),ex);
        }
    }

    public Object evalValue(String strExpression)
        throws EvalException
    {
        try
        {
            SyntaxNode node = parseExpression(strExpression);
            ValuePair p = null == node ? null : node.eval(this);
            return null == p ? null : p.getRealData();
        }
        catch(EvalException ex)
        {
            throw new EvalException(strExpression + ":" + ex.getMessage(),ex);
        }

        catch(LexException ex)
        {
            throw new EvalException(strExpression + ":" + ex.getMessage(),ex);
        }
        catch(SyntaxException ex)
        {
            throw new EvalException(strExpression + ":" + ex.getMessage(),ex);
        }
    }

}
